package com.xored.glance.search;

import java.util.HashMap;
import java.util.Map;

import com.xored.glance.core.ISourceListener;
import com.xored.glance.core.ITextBlock;
import com.xored.glance.core.ITextSource;
import com.xored.glance.core.Match;
import com.xored.glance.internal.core.TextSourceManager;
import com.xored.glance.search.engine.ISearchListener;
import com.xored.glance.search.engine.SearchEngine;
import com.xored.glance.search.engine.SearchRule;

/**
 * @author Yuri Strot
 * 
 */
public class SearchManager {

	/** Searching from the current position */
	public static final int FIND_HERE = 0;
	/** Searching next occurrence of the string */
	public static final int FIND_NEXT = 1;
	/** Searching previous occurrence of the string */
	public static final int FIND_PREVIOUS = 2;

	public SearchManager() {
		searchListener = new SearchListener();
		start();
	}

	public void setListener(ISearchListener listener) {
		this.listener = listener;
	}

	public void find(SearchRule rule, int type) {
		this.type = type;
		if (rule != null) {
			boolean textEquals = rule.isTextEquals(this.rule);
			boolean settingsEqual = rule.isSettingsEqual(this.rule);
			if (textEquals) {
				if (settingsEqual) {
					updateSelection();
				} else {
					updateSearch(rule, false);
				}
			} else {
				updateSearch(rule, true);
			}
		} else {
			updateSearch(rule, false);
		}
		this.rule = rule;
	}

	public void dispose() {
		TextSourceManager.getInstance().removeSourceListener(searchListener);
		if (engine != null) {
			engine.exit();
			engine = null;
		}
		if (source != null) {
			source.dispose();
			source = null;
		}
	}

	protected void start() {
		engine = new SearchEngine(searchListener);
		engine.start();
		TextSourceManager.getInstance().addSourceListener(searchListener);
		searchListener.sourceChanged(TextSourceManager.getInstance()
				.getSource());
	}

	private void initBlocks() {
		blockToOffset = new HashMap<ITextBlock, Integer>();
		if (source != null) {
			blocks = source.getBlocks();
			int offset = 0;
			for (int i = 0; i < blocks.length; i++) {
				ITextBlock block = blocks[i];
				blockToOffset.put(block, offset);
				offset += block.getText().length();
			}
		} else {
			this.blocks = new ITextBlock[0];
		}
	}

	protected void updateSearch(SearchRule rule, boolean paused) {
		matches = null;
		engine.setMatcher(rule, blocks, index, paused);
	}

	protected void findMatch(int index) {
		findMatch(matches[index]);
	}

	protected void findMatch(Match match) {
		if (match != null) {
			index = getOffset(match);
		}
		source.select(match);
	}

	protected void updateSelection() {
		if (matches != null) {
			if (type == FIND_NEXT) {
				for (int i = 0; i < matches.length; i++) {
					if (getOffset(matches[i]) > index) {
						findMatch(i);
						return;
					}
				}
				if (matches.length > 0) {
					findMatch(0);
				}
			} else if (type == FIND_PREVIOUS) {
				for (int i = matches.length - 1; i >= 0; i--) {
					if (getOffset(matches[i]) < index) {
						findMatch(i);
						return;
					}
				}
				if (matches.length > 0) {
					findMatch(matches.length - 1);
				}
			}
		}
	}

	protected int getOffset(Match match) {
		int offset = blockToOffset.get(match.getBlock());
		return offset + match.getOffset();
	}

	private class SearchListener implements ISearchListener, ISourceListener {

		public void allFound(Match[] matches) {
			SearchManager.this.matches = matches;
			updateSelection();
			listener.allFound(matches);
		}

		public void clear() {
			listener.clear();
			matches = null;
			source.show(Match.EMPTY);
		}

		public void finished() {
			listener.finished();
			source.show(matches);
		}

		public void firstFound(Match match) {
			if (type == FIND_HERE)
				findMatch(match);
			listener.firstFound(match);
		}

		public void sourceChanged(ITextSource source) {
			if (source == null)
				return;
			SearchManager.this.source = source;
			initBlocks();
		}

	}

	private SearchListener searchListener;
	private ISearchListener listener;
	private SearchEngine engine;
	private ITextSource source;
	private ITextBlock[] blocks;

	private Map<ITextBlock, Integer> blockToOffset;

	private Match[] matches;
	private int type;
	private int index;
	private SearchRule rule;

}
